Preprocesing data

Przekształanie i czyszczenie danych


Ten dokument opisuje sposoby, możliwości i techniki porządkowania i czyszczenia danych z użyciem biblioteki (Pandas) i (Polars) python. <br/
Mam nadzieję, że będzie to dla Ciebie przydatne!
Miłej lektury!
Author

Piotr Dłubak

Published

February 15, 2025

Cel analizy

  1. Badana populacja: klienci sklepów spożywczych
  2. Rodzaj badania: ankieta on-line dostępna dla wszystkich użytkowników na terenie Polski
  3. Metoda analizy: EDA (“Exploratory Data Analysis”) - analiza deskrypcyjna

Analiza deskrypcyjna to proces badania, opisywania i interpretacji danych w celu uzyskania wglądu i zrozumienia ich cech, wzorców i związków. Jest to technika często stosowana w dziedzinach naukowych, badań społecznych, statystyce, lingwistyce i wielu innych dziedzinach.

Głównym celem analizy deskrypcyjnej jest opisanie i podsumowanie danych w sposób, który ujawnia istotne informacje. Może obejmować takie elementy jak obliczanie średnich, median, odchyleń standardowych, minimalnych i maksymalnych wartości, oraz prezentowanie danych w postaci tabel, wykresów lub grafów.

Analiza deskrypcyjna umożliwia identyfikację kluczowych cech, trendów, anomalii i relacji w danych. Może również pomóc w odkrywaniu wzorców, porównywaniu grup lub kategorii danych oraz wyprowadzaniu wniosków na podstawie zebranych informacji.

Głównym celem EDA jest zapewnienie wglądu w dane jeszcze przed sformułowaniem jakichkolwiek założeń. Pomaga identyfikować oczywiste błędy, lepiej pojmować wzorce występujące w obrębie danych, wykrywać wartości odstające i anomalie, a także odnajdywać interesujące relacje między zmiennymi.

Po przeprowadzeniu analizy EDA i uzyskaniu istotnych spostrzeżeń wciąż można wykorzystać tę metodę do bardziej zaawansowanej analizy danych lub modelowania, w tym na potrzeby uczenia maszynowego.

Wyniki przeprowadzonej analizy pokazały, że dla współczesnego konsumenta decyzja o zakupie produktu nie jest motywowana wyłącznie chęcią zaspokojenia określonych potrzeb, ale w znacznym stopniu determinuje ją potrzeba demonstracji przekonań, statusu społeczno-ekonomicznego i stylu życia. Nowoczesny model konsumpcji niesie określone konsekwencje dla praktyki działań marketingowych.

Badanie ankietowe dotyczyło postaw i zachowań respondentów w tracie dokonywania zakupów produktów żywnościowych. W tym celu został skonstruowany kwestionariusz ankiety

Cechy, takie jak wiek, płeć, wykształcenie, preferowany typ sklepu, dochody netto, preferowana marka sklepu, preferowany towar, preferowany rodzaj promocji oraz czynnik zakupowy, mogą wpływać na wysokość zakupów żywnościowych.

Wymagania dotyczące danych: Zdefiniowenie zakresu analizy (zmienne, obserwacje, zakresy dziedzinowe)

#### Wymagania dotyczące danych

1. **Zmienne ilościowe:**
    - wiek: 13-105 lat
    - liczba osób w rodzinie: 1-20
    - dochody: >0
    - wydatki: >0

2. **Zmienne jakościowe:**
    - płeć: "mężczyzna", "kobieta"
    - wykształcenie: "podstawowe", "zawodowe", "średnie", "wyższe"
    - preferowany typ sklepu: "bazarek", "osiedlowy", "supermarket", "galeria"
    - preferowana marka sklepu: dowolne
    - preferowany towar: dowolne
    - czynnik zakupowy: dowolne
    - rodzaj promocji: "sugestia kasjera", "gazetka", "reklama RTV", "sms", "e-mail", "karta", "aplikacja", "nie korzystam"
    - miasto: dowolne
Nazwa zmiennej Wymagany typ danych Wartości referencyjne Czy puste? Powtórzenia?
nr_respondenta text, liczba dowolne niedozwolone niezozwolone
płeć text “mężczyzna”, “kobieta” niedozwolone dozwolone
wykształcenie text ‘podstawowe’, ‘zawodowe’,‘średnie’, ‘wyższe’ niedozwolone dozwolone
wiek liczba 13-105 niedozwolone dozwolone
liczba osób w rodzinie liczba (1-20) niedozwolone dozwolone
preferowany typ sklepu text ‘bazarek’, ‘osiedlowy’, ‘supermarket’, ‘galeria’ niedozwolone dozwolone
preferowana marka sklepu text dowolne niedozwolone dozwolone
preferowany towar text dowolne niedozwolone dozwolone
czynnik zakupowy text dowolne niedozwolone dozwolone
zakupy ile razy w mc text dowolne niedozwolone dozwolone
rodzaj promocji text ‘sugestia kasjera’, ‘gazetka’, ‘reklama RTV’, ‘sms’, ‘e-mail’, ‘karta’, ‘aplikacja’, ‘nie korzystam’ niezozwolone dozwolone
miasto text dowolne niedozwolone dozwolone
dochody liczba >0 niedozwolone dozwolone
wydatki liczba >0 niedozwolone dozwolone

Załadowanie bibliotek

Code
import warnings
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import statsmodels.api as sm
import random
from scipy.stats import zscore
import re
import math
warnings.filterwarnings("ignore")

from itables import init_notebook_mode, show




import itables.options as opt
# opt.layout =  {
#     "topStart": "pageLength",
#     "topEnd": "search",
#     "bottomStart": "info",
#     "bottomEnd": "paging"
# }  # (default value)
Note

Note that there are five types of callouts, including: note, tip, warning, caution, and important.

Tip

This is an example of an tip callout

Caution

This is an example of an caution callout

Warning

This is an example of an warning callout

Important

This is an example of an important callout

Remember to complete this section.

This information is crucial for understanding the concept.

You did a great job completing this task.

I. Wstępne przetwarzanie

1. Przygotowanie danych

1.1. Pozyskanie danych

  • Dane ankietowe do analizy pozyskano od klientow sklepów spożywczych w formie ankiety on-line udostępnionej dla wszystkich użytkowników na terenie Polski.
  • Informacje o danych demograficznych pozyskano z lokalnych baz danych organizatora badań.
  • Pozyskane dane zostały przekazane do organizatora celem zaladownia danych do plików.
  • W ten sposób do analizy otrzymano komplet 4 plików:
Plik
1 ‘ankieta_01a.xlsx’
2 ‘ankieta_01b.xlsx’
3 ‘ankieta_02.csv’
4 ‘miasta.json’
  • pliki te stanowią źródla informacji dla prowadzonej analizy EDA.

1.2. Pobranie i załadowanie danych

Code

ankieta_01a = pd.read_excel('ankieta_01a.xlsx')
ankieta_01b = pd.read_excel('ankieta_01b.xlsx')
ankieta_02 = pd.read_csv('ankieta_02.csv', skiprows=1, skipfooter=1, delimiter=';',engine='python')
miasta = pd.read_json('miasta.json')

1.3. Wyświetlenie pierwszych wierszy tabel

Code
display(ankieta_01a.head(4))

# show(ankieta_01a, "Tabela  - 1 częsc danych z pliku ankieta o1",layout={"topStart": "search", "topEnd": None},classes="display nowrap compact",
#      columnDefs=[{"width": "60%", "targets": "_all"}],
#     style="width:600px",
#     autoWidth=False,)
nr_respondenta kod_miasta wiek|liczba osób w rodzinie m_wykształcenie k_wykształcenie PREFEROWANY TYP SKLEPU preferowana marka sklepu preferowanay towar preferowany rodzaj promocji preferowany rodzaj promocji.1 czynnik zakupowy zakupy ile razy w mc
0 R_005 M_057 38|5 zawodowe NaN OSIEDLOWY LIDL Mięso i wędliny gazetka NaN jakość od 1 do 4
1 R_010 M_053 59|1 zawodowe NaN OSIEDLOWY NETTO Mięso i wędliny sms NaN marka od 1 do 4
2 R_036 M_074 35|1 wyższe NaN GALERIA ŻABKA Mięso i wędliny gazetka NaN jakość od 15 22
3 R_044 M_053 58|5 NaN podstawowe OSIEDLOWY BIEDRONKA Mięso i wędliny gazetka NaN marka od 1 do 4
Code
show(ankieta_01b,lengthMenu=[2, 5, 10, 20, 500])
nr_respondenta kod_miasta wiek|liczba osób w rodzinie m_wykształcenie k_wykształcenie PREFEROWANY TYP SKLEPU preferowana marka sklepu preferowanay towar preferowany rodzaj promocji preferowany rodzaj promocji.1 czynnik zakupowy zakupy ile razy w mc
Loading ITables v2.2.4 from the internet... (need help?)
Code
show(ankieta_02, scrollY="200px", scrollCollapse=True, paging=False)
nr_respondenta miara wartość
Loading ITables v2.2.4 from the internet... (need help?)
Code
# show(
#     ankieta_01b,
#     buttons=["copyHtml5", "csvHtml5", "excelHtml5","columnsToggle"],
# )

1.3. Wyświetlenie informacji o tabelach

Code


# Wyświetlenie typów zmiennych w DataFrame
types_df = pd.DataFrame(ankieta_01a.dtypes, columns=['Typ zmiennej']).reset_index()
types_df.columns = ['Nazwa zmiennej', 'Typ zmiennej']
types_df['Licz unikalnych wartości'] = ankieta_01a.nunique().values
types_df['Licz brakujących wartości'] = ankieta_01a.isnull().sum().values
types_df['Proc brakujących wartości'] = (ankieta_01a.isnull().mean() * 100).values
types_df['Proc brakujących wartości'] = types_df['Proc brakujących wartości'].round(2)
types_df['Unikalne wartości'] = types_df.apply(
lambda row: ankieta_01a[row['Nazwa zmiennej']].unique() if row['Typ zmiennej'] == 'object' else '', axis=1)
types_df = types_df.sort_values(by='Typ zmiennej')
display(types_df)
Nazwa zmiennej Typ zmiennej Licz unikalnych wartości Licz brakujących wartości Proc brakujących wartości Unikalne wartości
0 nr_respondenta object 59 0 0.00 [R_005, R_010, R_036, R_044, R_047, R_048, R_0...
1 kod_miasta object 39 0 0.00 [M_057, M_053, M_074, M_079, M_064, M_055, M_0...
2 wiek|liczba osób w rodzinie object 52 0 0.00 [38|5, 59|1, 35|1, 58|5, 26|3, 51|4, 28|3, 26|...
3 m_wykształcenie object 6 22 35.48 [zawodowe, wyższe, nan, podstawowe, średnie, p...
4 k_wykształcenie object 4 40 64.52 [nan, podstawowe, zawodowe, średnie, śred5nie]
5 PREFEROWANY TYP SKLEPU object 4 0 0.00 [OSIEDLOWY, GALERIA, BAZAREK, SUPERMARKET]
6 preferowana marka sklepu object 13 0 0.00 [LIDL, NETTO, ŻABKA, BIEDRONKA, JEŻYK, ALDI, P...
7 preferowanay towar object 7 0 0.00 [Mięso i wędliny, Napoje, Owoce i warzywa, Pro...
8 preferowany rodzaj promocji object 8 0 0.00 [gazetka, sms, reklama, sugestia, e-mail, kart...
9 preferowany rodzaj promocji.1 object 3 41 66.13 [nan, RTV, kasjera, korzystam]
10 czynnik zakupowy object 6 0 0.00 [jakość, marka, cena, dostępność, skład, lokal...
11 zakupy ile razy w mc object 11 0 0.00 [od 1 do 4, od 15 22, od 5 do 10, od 15 do 22...

2. Uporządkowanie danych

2.1.Określenie zasad uporządkowanego zbioru danych

Zasady Danych uporządkowanych: 1. Każda zmienna tworzy kolumnę. 2. Każda obserwacja tworzy wiersz. 3. Każdy rodzaj jednostki tworzy tabelę.

Dzięki uporządkowaniu danych: * łatwiej skuteczniej można przekształcać, zmieniać, Modelować, Wizualizować dane. * Uporządkowane dane zapewniają ustandaryzowany sposób. Łączenia struktury zestawu danych, jego fizyczny układ z jego semantyką. Jego znaczeniem. Struktura danych ramki danych składa się z wierszy i kolumn. * Kolumny oznaczają etykiety nazwy zmiennej * Wiersze zawierają daną. Opisywaną jednostkę obserwowaną.

Semantyka danych. * Zbiór danych to zbiór wartości. * Liczby w przypadku zmiennych ilościowych. * Ciągi znaków w przypadku zmiennych jakościowych. * Każda wartość należy do zmiennej i obserwacji. * Zmienna zawiera wszystkie wartości.

parametry badania: zmienne ilościowe: zmienne jakościowe:

2.2. Identyfikacja czy posiadane dane posiadają oznaki danych nieuporządkowych tj. są niezgodne z paradygmatem “TIDY DATA”

2.2.1. Niezgodność nr 1. Duplikaty

Sprawdzenie czy w danych występują duplikaty

Code

print(f"Nazwa pliku: --> ankieta_01a     duplikaty w pliku:--> {ankieta_01a.duplicated().any()}")
print(f"Nazwa pliku: --> ankieta_01b     duplikaty w pliku:--> {ankieta_01b.duplicated().any()}")
print(f"Nazwa pliku: --> ankieta_02      duplikaty w pliku:--> {ankieta_02.duplicated().any()}")
print(f"Nazwa pliku: --> miasta          duplikaty w pliku:--> {miasta.duplicated().any()}")
Nazwa pliku: --> ankieta_01a     duplikaty w pliku:--> True
Nazwa pliku: --> ankieta_01b     duplikaty w pliku:--> False
Nazwa pliku: --> ankieta_02      duplikaty w pliku:--> False
Nazwa pliku: --> miasta          duplikaty w pliku:--> False
Code

display(ankieta_01a[ankieta_01a.duplicated()])
nr_respondenta kod_miasta wiek|liczba osób w rodzinie m_wykształcenie k_wykształcenie PREFEROWANY TYP SKLEPU preferowana marka sklepu preferowanay towar preferowany rodzaj promocji preferowany rodzaj promocji.1 czynnik zakupowy zakupy ile razy w mc
8 R_059 M_016 26|5 NaN średnie SUPERMARKET ALDI Mięso i wędliny sugestia kasjera cena od 5 do 10
34 R_058 M_052 47|4 NaN średnie SUPERMARKET ŻABKA Produkty mleczne gazetka NaN marka od 11 do 15
50 R_057 M_054 69|2 NaN średnie OSIEDLOWY LIDL Produkty piekarnicze gazetka NaN cena od 11 do 15
Code
#Usunięcie zduplikowanych danych

print(f'UWAGA: Usunięto wiersze ({sum(ankieta_01a.duplicated())}) z duplikującymi się danymi')
ankieta_01a.drop_duplicates(inplace=True)
print(f'duplikaty w pliku:--> {ankieta_01a.duplicated().any()}')
UWAGA: Usunięto wiersze (3) z duplikującymi się danymi
duplikaty w pliku:--> False

2.2.2. Niezgodność nr 2. Podzielone obserwacje pomiędzy wiele tabel

Code
# dane z ankiety ankieta_01 zostały pozielona na dwa pliki:ankieta_01a i ankieta_01b

# Przed połączeniem należy sprawdzić zgodnośc tabel w zakresie kolumn

if list(ankieta_01a.columns) == list(ankieta_01a.columns):
    print("Kolumny są zgodne.")
else:
    print("Kolumny nie są zgodne.")
Kolumny są zgodne.
Code
#Połącznie plików: ankieta_01a i  ankieta_01b w jeden plik
ankieta_01 = pd.concat([ankieta_01a, ankieta_01b], axis=0)
ankieta_01.head(2)
Table 1: Astronomical object
nr_respondenta kod_miasta wiek|liczba osób w rodzinie m_wykształcenie k_wykształcenie PREFEROWANY TYP SKLEPU preferowana marka sklepu preferowanay towar preferowany rodzaj promocji preferowany rodzaj promocji.1 czynnik zakupowy zakupy ile razy w mc
0 R_005 M_057 38|5 zawodowe NaN OSIEDLOWY LIDL Mięso i wędliny gazetka NaN jakość od 1 do 4
1 R_010 M_053 59|1 zawodowe NaN OSIEDLOWY NETTO Mięso i wędliny sms NaN marka od 1 do 4

2.2.3. Niezgodność nr 3. Przechowywanie w rożnych wierszach jednej kolumny wartosci wielu zmiennych

Code
ankieta_02.head(2)
Table 2: wjednej kolumnie “miara” przechowywane są nazwy 2 różnych zmiennych a w kolumnie “wartość” wartosci odpowiadające różnym zmiennych
nr_respondenta miara wartość
0 R_001 dochody roczne 54
1 R_001 wydatki_na_żywność_mc 750
Code
# przekształcenie tabeli
ankieta_02 = ankieta_02.set_index(['nr_respondenta', 'miara']).unstack().reset_index()
ankieta_02.head(2)
nr_respondenta wartość
miara dochody roczne wydatki_na_żywność_mc
0 R_001 54 750
1 R_002 26 2300

2.2.4. Niezgodność nr 4. Używanie jaka nagłówków kolumn wartosci zamiast nazw zmiennych

Code
# Zmienna wykształcenie i płeć zostały połączone w k=jednej kolumnie
ankieta_01[['nr_respondenta','m_wykształcenie','k_wykształcenie']].head(3)
nr_respondenta m_wykształcenie k_wykształcenie
0 R_005 zawodowe NaN
1 R_010 zawodowe NaN
2 R_036 wyższe NaN
Code
t1=ankieta_01[['nr_respondenta','m_wykształcenie','k_wykształcenie']]
t1=t1.set_index('nr_respondenta').stack().reset_index()
t1.columns  = ['nr_respondenta', 'typ_wykształcenia', 'wykształcenie']
t1['typ_wykształcenia'] = t1['typ_wykształcenia'].str.replace('_wykształcenie', '')

t1 = t1.rename(columns={'typ_wykształcenia': 'płeć'})

ankieta_01 = pd.merge(ankieta_01, t1, on = 'nr_respondenta',how = 'left'    )
ankieta_01 = ankieta_01.drop(['m_wykształcenie','k_wykształcenie'], axis=1)
ankieta_01.head(2)
nr_respondenta kod_miasta wiek|liczba osób w rodzinie PREFEROWANY TYP SKLEPU preferowana marka sklepu preferowanay towar preferowany rodzaj promocji preferowany rodzaj promocji.1 czynnik zakupowy zakupy ile razy w mc płeć wykształcenie
0 R_005 M_057 38|5 OSIEDLOWY LIDL Mięso i wędliny gazetka NaN jakość od 1 do 4 m zawodowe
1 R_010 M_053 59|1 OSIEDLOWY NETTO Mięso i wędliny sms NaN marka od 1 do 4 m zawodowe

2.2.5. Niezgodność nr 5. Podzielone wartości jednej zmiennej pomiędzy kilka kolumn

Code
#Zmienna preferowany rodzaj promocji została podzielona na dwie kolumny
ankieta_01[['preferowany rodzaj promocji','preferowany rodzaj promocji.1']].head(2)
preferowany rodzaj promocji preferowany rodzaj promocji.1
0 gazetka NaN
1 sms NaN
Code
ankieta_01['preferowany rodzaj promocji.1']=ankieta_01['preferowany rodzaj promocji.1'].fillna('_')
ankieta_01['rodzaj promocji'] = ankieta_01['preferowany rodzaj promocji'].astype(str).str.cat(ankieta_01['preferowany rodzaj promocji.1'].astype(str), sep=' ')
ankieta_01['rodzaj promocji'] = ankieta_01['rodzaj promocji'].str.replace('_','')    # usunieto zbędne znaki_
ankieta_01= ankieta_01.drop('preferowany rodzaj promocji', axis=1) # usunięcie zbędnej kolumny
ankieta_01= ankieta_01.drop('preferowany rodzaj promocji.1', axis=1) # usunięcie zbędnej kolumny

2.2.6. Niezgodność nr 6. Przechowywanie w jednej kolumnie połączonych wartosci wielu zmiennych.

Code

ankieta_01[['wiek|liczba osób w rodzinie']].head(3)
wiek|liczba osób w rodzinie
0 38|5
1 59|1
2 35|1
Code
ankieta_01[['wiek', 'liczba osób w rodzinie']] = ankieta_01['wiek|liczba osób w rodzinie'].str.split('|', expand=True) # podzielenie kolumny
ankieta_01= ankieta_01.drop('wiek|liczba osób w rodzinie', axis=1) # usunięcie zbędnej kolumny

2.2.7. Niezgodność nr 7. Zmienne podzielonone pomiędzy wiele tabel

Code
display(ankieta_01.head(2))
display(ankieta_02.head(2))
display(miasta.head(2))
nr_respondenta kod_miasta PREFEROWANY TYP SKLEPU preferowana marka sklepu preferowanay towar czynnik zakupowy zakupy ile razy w mc płeć wykształcenie rodzaj promocji wiek liczba osób w rodzinie
0 R_005 M_057 OSIEDLOWY LIDL Mięso i wędliny jakość od 1 do 4 m zawodowe gazetka 38 5
1 R_010 M_053 OSIEDLOWY NETTO Mięso i wędliny marka od 1 do 4 m zawodowe sms 59 1
nr_respondenta wartość
miara dochody roczne wydatki_na_żywność_mc
0 R_001 54 750
1 R_002 26 2300
kod_miasta Miasto Liczba ludności
0 M_001 Warszawa 1790658
1 M_002 Kraków 779115
Code
ankieta_01 = pd.merge(ankieta_01, miasta, on = 'kod_miasta',how = 'left')
#ankieta_01= ankieta_01.drop('kod_miasta', axis=1) 
Code
ankieta_02.columns= ankieta_02.columns.to_flat_index()
Code
nowe_nazwy = {  
('wartość', 'dochody roczne'):'dochody',
('wartość', 'wydatki_na_żywność_mc'):'wydatki',
('nr_respondenta', '') :'nr_respondenta'
}
# Zmiana nazw kolumn na podstawie słownika
ankieta_02 = ankieta_02.rename(columns=nowe_nazwy)
Code

ankieta_01= pd.merge(ankieta_01,ankieta_02, on = 'nr_respondenta')
ankieta_01.head(2)
nr_respondenta kod_miasta PREFEROWANY TYP SKLEPU preferowana marka sklepu preferowanay towar czynnik zakupowy zakupy ile razy w mc płeć wykształcenie rodzaj promocji wiek liczba osób w rodzinie Miasto Liczba ludności dochody wydatki
0 R_005 M_057 OSIEDLOWY LIDL Mięso i wędliny jakość od 1 do 4 m zawodowe gazetka 38 5 Ostrowiec Świętokrzyski 69715 28 750
1 R_010 M_053 OSIEDLOWY NETTO Mięso i wędliny marka od 1 do 4 m zawodowe sms 59 1 Lubin 78937 30 590
Code
dane=ankieta_01.copy()
baza = dane.copy()

3.Czyszczenie danych

3.1 Wprowadzenie

  1. Zdefiniować, czym są czyste dane:
  • Dane musza być dokładne.
  • Kompletne.
  • Spójne.
  • Ważne.
  • Aktualne.
  • Bez duplikatów.
  • Jednolite.
  1. Rozpoznać „brudne” dane

Mozliwe oznaki zanieczyszczonych danych

a)  Różna pisownia wariantów zmiennej kategorycznej płeć.
b)  Zbędne myślniki.
c)  Litery drukowane
d)  Wartości niezgodne z rzeczywistością 
e)  Zbędne odstępy. 
f)  Czeskie błędy.
g)  Dane niezgodne z wiedzą dziedzinową.
h)  Dane niezgodne ze zdrowym rozsądkiem.
i)  Wartości liczbowe we wartościach zmiennej kategorycznej, gdzie powinna być wartość? Tekstowa.
j)  Wartości tekstowe w zmiennych ilościowych.
k)  Duplikaty.
l)  Myślniki zamiast wartości.
m)  Zera zamiast wartości.
n)  Wartości brakujące-puste.
o)  Różne jednostki jednej zmiennej.

Odpowiedzią na ww. nieprawidłowości będą odpowiednie oczyszczenie danych z błędów.

3.2. Błędne dane

3.2.1. Czyszczenie i uporządkowanie nazw kolumn

Code
baza.columns = baza.columns.str.lower().str.replace("_"," ")

kolejnosc = ['nr respondenta', 'płeć', 'wykształcenie', 'wiek','liczba osób w rodzinie','preferowany typ sklepu', 'preferowana marka sklepu',
 'preferowanay towar', 'czynnik zakupowy', 'zakupy ile razy w mc','rodzaj promocji', 'miasto','liczba ludności','dochody', 'wydatki']

baza= baza[kolejnosc]

3.2.2. Błędy - zmienna tekstowa

Code

zmienna_tekstowa= ['płeć', 'wykształcenie','preferowany typ sklepu','preferowana marka sklepu', 'preferowanay towar', 'czynnik zakupowy',
 'rodzaj promocji']

#Zamiana drukowanych liter na małe

baza[zmienna_tekstowa]=baza[zmienna_tekstowa].applymap(lambda x: x.lower() if isinstance(x, str) else x)


zmienna_liczbowa = [ 'wiek','liczba osób w rodzinie','liczba ludności',
'dochody', 'wydatki', 'od', 'do', 'średnia częstość zakupów w tyg']

# wyszukanie niezozwolonych znaków 

#znaki= [[0-9]|\s|_|/.|,|\.|\?|:|;|>|<|\+|=|{|}|-|<|>| !"#$%&'()*+,-./:;<=>?@[\]^_{|}~]    ('^[A-Z]+$|\W|_|\d',

znaki = (r'[A-Z]|[0-9]|_|,|W|\.|\?|-')
                               
print(f'Dane, które zawierają niedozwolone znaki: {znaki}')
print('='*60)
for zmienna in zmienna_tekstowa:
    czy_inne_znaki = baza[zmienna].str.contains(znaki, na=False)

    if any(czy_inne_znaki) == True:
        display(baza.loc[czy_inne_znaki, ['nr respondenta',zmienna]])
Dane, które zawierają niedozwolone znaki: [A-Z]|[0-9]|_|,|W|\.|\?|-
============================================================
nr respondenta wykształcenie
48 R_003 podstawowe.
58 R_053 śred5nie
113 R_095 wyższe,
nr respondenta rodzaj promocji
9 R_012 e-mail
50 R_026 e-mail
51 R_045 e-mail
Code
# poprawa
znaki = (r'[A-Z]|[0-9]|_|,|W|\.|\?|-')

zmienna_tekstowa= ['płeć', 'wykształcenie','preferowany typ sklepu','preferowana marka sklepu', 'preferowanay towar', 'czynnik zakupowy',
 'rodzaj promocji']

for zmienna in zmienna_tekstowa:
    czy_inne_znaki = baza[zmienna].str.contains(znaki, na=False)
    if any(czy_inne_znaki) == True:
        baza[zmienna] = baza[zmienna].str.replace(znaki,"")
Code
baza['preferowany typ sklepu'].value_counts()
preferowany typ sklepu
supermarket    34
osiedlowy      30
galeria        28
bazarek        27
super           2
bazar           2
Name: count, dtype: int64

3.2.3. Niezgodności ref. - zmienna tekstowa

Code

płeć_ref = ['m', 'k']
preferowany_typ_sklepu_ref = ['bazarek', 'osiedlowy', 'supermarket', 'galeria']
wykształcenie_ref = ['podstawowe', 'zawodowe', 'średnie', 'wyższe']

niepasujące_dane_kolumna1 = baza.loc[~baza['płeć'].isin(płeć_ref)]
niepasujące_dane_kolumna2 = baza.loc[~baza['preferowany typ sklepu'].isin(preferowany_typ_sklepu_ref)]
niepasujące_dane_kolumna3 = baza.loc[~baza['wykształcenie'].isin(wykształcenie_ref)]

if not niepasujące_dane_kolumna1.empty:
    print("Niepasujące dane w kolumnie 'płeć':")
    display(niepasujące_dane_kolumna1[['płeć']])

if not niepasujące_dane_kolumna2.empty:
    print("Niepasujące dane w kolumnie 'preferowany typ sklepu':")
    display(niepasujące_dane_kolumna2[['preferowany typ sklepu']])

if not niepasujące_dane_kolumna3.empty:
    print("Niepasujące dane w kolumnie 'wykształcenie':")
    display(niepasujące_dane_kolumna3[['wykształcenie']])
Niepasujące dane w kolumnie 'preferowany typ sklepu':
preferowany typ sklepu
86 super
91 super
97 bazar
108 NaN
118 bazar
120 NaN
Niepasujące dane w kolumnie 'wykształcenie':
wykształcenie
48 podstawowe.
49 średn
58 śred5nie
93 w
113 wyższe,
116 ś

3.2.3. Obsługa błędów w zmiennych tekstowych

Code

dict_wyk={'średn':'średnie', 'ś': 'średnie', 'w':'wyższe'}
baza['wykształcenie'] = baza['wykształcenie'].replace(dict_wyk)

preferowany_typ_sklepu_ref = {'super': 'supermarket', 'bazar':'bazarek'}
baza['preferowany typ sklepu'] = baza['preferowany typ sklepu'].replace(preferowany_typ_sklepu_ref)
Code
baza['preferowana marka sklepu'] = baza['preferowana marka sklepu'].str.title()
Code

custom_order = ['podstawowe', 'zawodowe','średnie', 'wyższe']
cat_dtype = pd.CategoricalDtype(categories=custom_order, ordered=True)

# Zmiana typu kolumny na "categorical" z zdefiniowanym porządkiem
baza['wykształcenie'] = baza['wykształcenie'].astype(cat_dtype)

zmienna_tekstowa= ['płeć','preferowany typ sklepu','preferowana marka sklepu', 'preferowanay towar', 'czynnik zakupowy',
 'rodzaj promocji']

baza[zmienna_tekstowa]=baza[zmienna_tekstowa].astype('category')
Code
baza['wykształcenie'].cat.categories
Index(['podstawowe', 'zawodowe', 'średnie', 'wyższe'], dtype='object')
Code
# Podzielenie wartości liczbowych 
baza[['od', 'do']] = baza['zakupy ile razy w mc'].str.extract(r'(\d+\.\d+|\d+)[^\d]*(\d+\.\d+|\d+)')

baza['średnia częstość zakupów w tyg'] = (baza['od'].astype(float) + baza['do'].astype(float)) / 2
baza['średnia częstość zakupów w tyg'] = baza['średnia częstość zakupów w tyg'].apply(math.ceil)
display(baza[['od', 'do']].head())
baza[['nr respondenta', 'od', 'do', 'średnia częstość zakupów w tyg']]
baza.drop(['od', 'do', 'zakupy ile razy w mc'], axis=1, inplace=True)
baza.head(3)
od do
0 1 4
1 1 4
2 15 22
3 1 4
4 5 10
nr respondenta płeć wykształcenie wiek liczba osób w rodzinie preferowany typ sklepu preferowana marka sklepu preferowanay towar czynnik zakupowy rodzaj promocji miasto liczba ludności dochody wydatki średnia częstość zakupów w tyg
0 R_005 m zawodowe 38 5 osiedlowy Lidl mięso i wędliny jakość gazetka Ostrowiec Świętokrzyski 69715 28 750 3
1 R_010 m zawodowe 59 1 osiedlowy Netto mięso i wędliny marka sms Lubin 78937 30 590 3
2 R_036 m wyższe 35 1 galeria Żabka mięso i wędliny jakość gazetka Biała Podlaska 57957 35 753 19

3.2.4. Błędy - zmienna liczbowa

Code
zmienna_liczbowa = ['wiek', 'liczba osób w rodzinie', 'dochody', 'wydatki', 'średnia częstość zakupów w tyg']

for kolumna in zmienna_liczbowa:
    czy_inne_znaki = baza[kolumna].astype(str).str.contains('\D')
    print(f'"{kolumna}" zawiera znaki nie liczbowe : --> {any(czy_inne_znaki)}')
    if any(czy_inne_znaki):
        display(baza.loc[czy_inne_znaki, ['nr respondenta', kolumna]])
"wiek" zawiera znaki nie liczbowe : --> True
nr respondenta wiek
9 R_012 33 lata
40 R_004 67 lat
"liczba osób w rodzinie" zawiera znaki nie liczbowe : --> False
"dochody" zawiera znaki nie liczbowe : --> True
nr respondenta dochody
24 R_011 -31
52 R_007 29 zł
"wydatki" zawiera znaki nie liczbowe : --> True
nr respondenta wydatki
23 R_006 -556
28 R_021 669 PLN
"średnia częstość zakupów w tyg" zawiera znaki nie liczbowe : --> False
Code
#pOPRAWA 
# # Numer indeksów, dla których chcemy zmienić wartości na NaN
# indexes_to_replace = [10,5,34,35,77,103,1]

# for index in indexes_to_replace:
#     baza.at[index, 'dochód'] = np.nan


def konwertuj_na_int(wartosc):
    return int(wartosc.replace(' zł', '').replace(' PLN', '').replace(' ', ''))

# Konwersja wartości w kolumnie 'dochody' na int
baza['dochody'] = baza['dochody'].apply(konwertuj_na_int)

# Konwersja wartości w kolumnie 'wydatki' na int
baza['wydatki'] = baza['wydatki'].apply(konwertuj_na_int)
Code

# Konwersja zmiennych z 'object' na 'int64'
baza['wiek'] = pd.to_numeric(baza['wiek'], errors='coerce').astype('float')
baza['liczba osób w rodzinie'] = pd.to_numeric(baza['liczba osób w rodzinie'], errors='coerce').astype('float')

print(baza.dtypes)

baza['dochody']=baza['dochody']*1000
baza['wydatki']=baza['wydatki']*12
nr respondenta                      object
płeć                              category
wykształcenie                     category
wiek                               float64
liczba osób w rodzinie             float64
preferowany typ sklepu            category
preferowana marka sklepu          category
preferowanay towar                category
czynnik zakupowy                  category
rodzaj promocji                   category
miasto                              object
liczba ludności                      int64
dochody                              int64
wydatki                              int64
średnia częstość zakupów w tyg       int64
dtype: object

3.2.5. Niezgodności ref. - zmienna liczbowa

Code
#dozwolone przedziały i wartosci
# 'wiek', 18 -105
# 'liczba osób w rodzinie', 1-10
# 'dochody', [> 0]
# 'wydatki', [> 0 ]
# dochody > wydatki

import pandas as pd

# Sprawdzanie niezgodności w zmiennej 'dochody'
czy_ujemne = (baza['dochody'] < 0)
czy_zero = (baza['dochody'] == 0)
if any(czy_ujemne) or any(czy_zero):
    print(f'Zmienna "dochody" zawiera wartości < 0 lub = 0.')
    display(baza.loc[czy_ujemne | czy_zero, ['nr respondenta', 'dochody']])
else:
    print('Brak niezgodności w zmiennej "dochody".')

# Sprawdzanie niezgodności w zmiennej 'wydatki'
czy_ujemne = (baza['wydatki'] < 0)
if any(czy_ujemne):
    print(f'Zmienna "wydatki" zawiera wartości < 0.')
    display(baza.loc[czy_ujemne, ['nr respondenta', 'wydatki']])
else:
    print('Brak niezgodności w zmiennej "wydatki".')

# Sprawdzanie niezgodności między 'wydatki' a 'dochody'
czy_wieksze_wydatki = baza['wydatki'] > baza['dochody']
if any(czy_wieksze_wydatki):
    print('Istnieją wydatki większe od dochodów.')
    display(baza.loc[czy_wieksze_wydatki, ['nr respondenta', 'dochody', 'wydatki']])
else:
    print('Brak niezgodności między wydatkami a dochodami.')

# Sprawdzanie niezgodności w zmiennej 'wiek'
czy_poza_zakresem = (baza['wiek'] < 15) | (baza['wiek'] > 105)
if any(czy_poza_zakresem):
    print('Zmienna "wiek" zawiera wartości spoza zakresu 15-105.')
    display(baza.loc[czy_poza_zakresem, ['nr respondenta', 'wiek']])
else:
    print('Brak niezgodności w zmiennej "wiek".')

# Sprawdzanie niezgodności w zmiennej 'liczba osób w rodzinie'
czy_poza_zakresem = (baza['liczba osób w rodzinie'] < 1) | (baza['liczba osób w rodzinie'] > 10)
if any(czy_poza_zakresem):
    print('Zmienna "liczba osób w rodzinie" zawiera wartości spoza zakresu 1-10.')
    display(baza.loc[czy_poza_zakresem, ['nr respondenta', 'liczba osób w rodzinie']])
else:
    print('Brak niezgodności w zmiennej "liczba osób w rodzinie".')
Zmienna "dochody" zawiera wartości < 0 lub = 0.
nr respondenta dochody
23 R_006 0
24 R_011 -31000
82 R_104 0
109 R_078 0
Zmienna "wydatki" zawiera wartości < 0.
nr respondenta wydatki
23 R_006 -6672
Istnieją wydatki większe od dochodów.
nr respondenta dochody wydatki
22 R_002 26000 27600
24 R_011 -31000 7092
82 R_104 0 12024
109 R_078 0 10692
Zmienna "wiek" zawiera wartości spoza zakresu 15-105.
nr respondenta wiek
52 R_007 158.0
Zmienna "liczba osób w rodzinie" zawiera wartości spoza zakresu 1-10.
nr respondenta liczba osób w rodzinie
13 R_040 30.0

3.2.6. Obsługa błędów w zmiennych liczbowych

Z tych danych wynika, że są to błędne wpisy. Z uwagi na niewielką ilość pozyskanych danych usuwanie takich danych znacznie zmniejszyło by liczbę dostapych danych, dlatego zostanie dokonana zamiana błędych wartosci na wartości brakujące, następnie w dalszych krokach braki zostaną obsłużone.

Code

# # Numer indeksów, dla których chcemy zmienić wartości na NaN
indexes_to_replace = [23,24,82,109]

for index in indexes_to_replace:
    baza.at[index, 'dochody'] = np.nan
Code

# # Numer indeksów, dla których chcemy zmienić wartości na NaN
indexes_to_replace = [23]

for index in indexes_to_replace:
    baza.at[index, 'wydatki'] = np.nan
Code
# # Numer indeksów, dla których chcemy zmienić wartości na NaN
indexes_to_replace = [52]

for index in indexes_to_replace:
    baza.at[index, 'wiek'] = np.nan
Code
# # Numer indeksów, dla których chcemy zmienić wartości na NaN
indexes_to_replace = [13]

for index in indexes_to_replace:
    baza.at[index, 'liczba osób w rodzinie'] = np.nan
Code
# # Numer indeksów, dla których chcemy zmienić wartości na NaN
indexes_to_replace = [52,22]

for index in indexes_to_replace:
    baza.at[index, 'wydatki'] = np.nan
Code

import pandas as pd

# Sprawdzanie niezgodności w zmiennej 'dochody'
czy_ujemne = (baza['dochody'] < 0)
czy_zero = (baza['dochody'] == 0)
if any(czy_ujemne) or any(czy_zero):
    print(f'Zmienna "dochody" zawiera wartości < 0 lub = 0.')
    display(baza.loc[czy_ujemne | czy_zero, ['nr respondenta', 'dochody']])
else:
    print('Brak niezgodności w zmiennej "dochody".')

# Sprawdzanie niezgodności w zmiennej 'wydatki'
czy_ujemne = (baza['wydatki'] < 0)
if any(czy_ujemne):
    print(f'Zmienna "wydatki" zawiera wartości < 0.')
    display(baza.loc[czy_ujemne, ['nr respondenta', 'wydatki']])
else:
    print('Brak niezgodności w zmiennej "wydatki".')

# Sprawdzanie niezgodności między 'wydatki' a 'dochody'
czy_wieksze_wydatki = baza['wydatki'] > baza['dochody']
if any(czy_wieksze_wydatki):
    print('Istnieją wydatki większe od dochodów.')
    display(baza.loc[czy_wieksze_wydatki, ['nr respondenta', 'dochody', 'wydatki']])
else:
    print('Brak niezgodności między wydatkami a dochodami.')

# Sprawdzanie niezgodności w zmiennej 'wiek'
czy_poza_zakresem = (baza['wiek'] < 15) | (baza['wiek'] > 105)
if any(czy_poza_zakresem):
    print('Zmienna "wiek" zawiera wartości spoza zakresu 15-105.')
    display(baza.loc[czy_poza_zakresem, ['nr respondenta', 'wiek']])
else:
    print('Brak niezgodności w zmiennej "wiek".')

# Sprawdzanie niezgodności w zmiennej 'liczba osób w rodzinie'
czy_poza_zakresem = (baza['liczba osób w rodzinie'] < 1) | (baza['liczba osób w rodzinie'] > 10)
if any(czy_poza_zakresem):
    print('Zmienna "liczba osób w rodzinie" zawiera wartości spoza zakresu 1-10.')
    display(baza.loc[czy_poza_zakresem, ['nr respondenta', 'liczba osób w rodzinie']])
else:
    print('Brak niezgodności w zmiennej "liczba osób w rodzinie".')
Brak niezgodności w zmiennej "dochody".
Brak niezgodności w zmiennej "wydatki".
Brak niezgodności między wydatkami a dochodami.
Brak niezgodności w zmiennej "wiek".
Brak niezgodności w zmiennej "liczba osób w rodzinie".

3.3. Brakujące dane

3.3.1 Sprawdzenie czy w danych wystepują braki (Null, None) [razem, w wierszach , w kolummnach]

Code
def braki_sprawdzenie(dane):
    """
    Analizuje brakujące dane w tabeli.

    Parametry:
        dane (DataFrame): Ramka danych do analizy.

    Zwraca:
        None
    """
    liczba = dane.isnull().sum().sum()
    proc = (liczba / (dane.shape[0]*dane.shape[1])*100).round(2)
    if liczba == 0:
        print('Analiza brakujących danych:')
        print('='*45)
        print('W tabeli nie stwierdzono brakujących danych!')
    else:
        print('Analiza brakujących danych:')
        print('='*45)
        print(f'Liczba brakujących danych w tabeli: {liczba}')
        print(f'Procent brakujących danych w tabeli: {proc}%')
        print('='*45)
        rows_with_missing_data = dane[dane.isnull().any(axis=1)]
        brakujace_dane = rows_with_missing_data.isnull().sum(axis=0)
        udzial_brakujacych_danych = ((rows_with_missing_data.isnull().sum(axis=0) / dane.shape[0])*100).round(1)
        wyniki = pd.DataFrame({'liczba': brakujace_dane, 'proc': udzial_brakujacych_danych})
        print('Brakujące dane w zmiennych (kolumny):')
        display(wyniki)
        rows_with_missing_data = dane[dane.isnull().any(axis=1)]
        brakujace_dane = rows_with_missing_data.isnull().sum(axis=1)
        udzial_brakujacych_danych = (rows_with_missing_data.isnull().sum(axis=1) / dane.shape[1]*100).round(1)
        wyniki = pd.DataFrame({'liczba': brakujace_dane, 'proc': udzial_brakujacych_danych})
        print('='*45)
        print('Brakujące dane w obserwacjach (wiersze):')
        display(wyniki)
        print('='*45)
        fig, ax = plt.subplots(figsize=(9.5, 4))
        sns.heatmap(dane.isnull(), cmap='coolwarm', ax=ax)
        rows_with_missing_data = dane[dane.isnull().any(axis=1)]
        print('Tabela z brakującymi danymi:')
        display(rows_with_missing_data)
    
braki_sprawdzenie(baza)

        
Analiza brakujących danych:
=============================================
Liczba brakujących danych w tabeli: 18
Procent brakujących danych w tabeli: 0.96%
=============================================
Brakujące dane w zmiennych (kolumny):
liczba proc
nr respondenta 0 0.0
płeć 0 0.0
wykształcenie 3 2.4
wiek 3 2.4
liczba osób w rodzinie 1 0.8
preferowany typ sklepu 2 1.6
preferowana marka sklepu 2 1.6
preferowanay towar 0 0.0
czynnik zakupowy 0 0.0
rodzaj promocji 0 0.0
miasto 0 0.0
liczba ludności 0 0.0
dochody 4 3.2
wydatki 3 2.4
średnia częstość zakupów w tyg 0 0.0
=============================================
Brakujące dane w obserwacjach (wiersze):
liczba proc
9 1 6.7
13 1 6.7
22 1 6.7
23 2 13.3
24 1 6.7
40 1 6.7
48 1 6.7
52 2 13.3
58 1 6.7
82 1 6.7
108 2 13.3
109 1 6.7
113 1 6.7
120 2 13.3
=============================================
Tabela z brakującymi danymi:
nr respondenta płeć wykształcenie wiek liczba osób w rodzinie preferowany typ sklepu preferowana marka sklepu preferowanay towar czynnik zakupowy rodzaj promocji miasto liczba ludności dochody wydatki średnia częstość zakupów w tyg
9 R_012 m zawodowe NaN 5.0 bazarek Biedronka napoje marka e-mail Gorzów Wielkopolski 124581 30000.0 9000.0 3
13 R_040 m wyższe 69.0 NaN osiedlowy Biedronka napoje marka reklama rtv Piotrków Trybunalski 76279 38000.0 9144.0 13
22 R_002 m podstawowe 16.0 1.0 bazarek Netto owoce i warzywa dostępność gazetka Suwałki 69442 26000.0 NaN 3
23 R_006 m zawodowe 28.0 1.0 bazarek Dino owoce i warzywa jakość reklama rtv Warszawa 1790658 NaN NaN 8
24 R_011 m zawodowe 24.0 3.0 osiedlowy Kaufland owoce i warzywa skład gazetka Włocławek 115561 NaN 7092.0 3
40 R_004 m podstawowe NaN 1.0 osiedlowy Lidl produkty piekarnicze jakość gazetka Stargard 71464 45000.0 6084.0 8
48 R_003 m NaN 49.0 1.0 bazarek Biedronka produkty piekarnicze jakość reklama rtv Świdnica 60281 50000.0 5160.0 3
52 R_007 m zawodowe NaN 5.0 bazarek Lidl produkty zbożowe marka gazetka Olsztyn 170904 29000.0 NaN 3
58 R_053 k NaN 21.0 3.0 osiedlowy Dino słodycze jakość gazetka Jaworzno 94731 41000.0 9780.0 3
82 R_104 k wyższe 77.0 5.0 galeria Dino owoce i warzywa opakowanie reklama rtv Nowy Sącz 84270 NaN 12024.0 8
108 R_112 k wyższe 53.0 5.0 NaN NaN słodycze jakość nan Elbląg 119144 54000.0 12936.0 19
109 R_078 k wyższe 25.0 2.0 supermarket Netto słodycze jakość nie korzystam Radomsko 49898 NaN 10692.0 3
113 R_095 k NaN 75.0 4.0 osiedlowy Biedronka mięso i wędliny jakość reklama rtv Słupsk 93460 48000.0 11544.0 19
120 R_118 k wyższe 63.0 2.0 NaN NaN produkty zbożowe jakość nan Ełk 60252 57000.0 14964.0 3

3.3.2. Obsługa brakujących danych (usuwanie [całość, wg progu], imputacja, utworzenie nowej kategorii np. “b.d.”)

W danych znajdują się barkujące dane.Z uwagi na niewielką ilość pozyskanych danych usuwanie takich danych znacznie zmniejszyło by liczbę dostępych danych, dlatego zostanie dokonana imputacja.  Natomiast w przypadku wierszy  i kolumn , w których znajduje się  = >20 % braków o sustaną usuniete, ponieważ tak znaczna ilość braków może nie być losowa, a imputacja mogła by bardzo zniekształcić oryginalny rozkład 
Code

baza['dochody'].fillna(baza['dochody'].mean().round(1), inplace=True)
baza['wydatki'].fillna(baza['wydatki'].mean().round(1), inplace=True)
baza['wiek'].fillna(baza['wiek'].mode()[0], inplace=True)
baza['liczba osób w rodzinie'].fillna(baza['liczba osób w rodzinie'].mode()[0], inplace=True)
baza['preferowany typ sklepu'].fillna(baza['preferowany typ sklepu'].mode()[0], inplace=True)
baza['preferowana marka sklepu'].fillna(baza['preferowana marka sklepu'].mode()[0], inplace=True)
baza['wykształcenie'].fillna(baza['wykształcenie'].mode()[0], inplace=True)
Code
braki_sprawdzenie(baza)
Analiza brakujących danych:
=============================================
W tabeli nie stwierdzono brakujących danych!

3.4. Obserwacje nietypowe i odstające

3.4.1. Sprawdzenie czy w zmiennych liczbowych wystepują obserwacje odstające ( naturalne, błędy)

Code
def outliers(df, var):
    data = df[var]
 
    def detect_outliers_iqr(data):
        q1, q3 = np.percentile(data, [25, 75])
        iqr = q3 - q1
        lower_bound = q1 - (1.5 * iqr)
        upper_bound = q3 + (1.5 * iqr)
        outliers_IQR = [i for i, x in enumerate(data) if x < lower_bound or x > upper_bound]
        return outliers_IQR

    def detect_outliers_mean_std(data):
        mean = np.mean(data)
        std = np.std(data)
        lower_bound = mean - (3 * std)
        upper_bound = mean + (3 * std)
        outliers_mean_std = [i for i, x in enumerate(data) if x < lower_bound or x > upper_bound]
        return outliers_mean_std

    def detect_outliers_zscore(data):
        threshold = 2
        z_scores = zscore(data)
        outliers_zscore = [i for i, z in enumerate(z_scores) if abs(z) > threshold]
        return outliers_zscore

    def detect_outliers_winsorizing(data):
        lower_bound, upper_bound = np.percentile(data, [5, 95])
        data = np.clip(data, lower_bound, upper_bound)
        outliers_winsorizing = [i for i, x in enumerate(data) if x < lower_bound or x > upper_bound]
        return outliers_winsorizing

    # Reshape the data to a 2D array
    data = np.array(data).reshape(-1, 1)

    outliers_IQR = detect_outliers_iqr(data)
    outliers_mean_std = detect_outliers_mean_std(data)
    outliers_zscore = detect_outliers_zscore(data)
    outliers_winsorizing = detect_outliers_winsorizing(data)

    df = pd.DataFrame(data)
    df['Odstające_IQR'] = np.where(df.index.isin(outliers_IQR), -1, 1)
    df['Odstające_mean_std'] = np.where(df.index.isin(outliers_mean_std), -1, 1)
    df['Odstające_Zscore'] = np.where(df.index.isin(outliers_zscore), -1, 1)
    df['Odstające_Winsorizing'] = np.where(df.index.isin(outliers_winsorizing), -1, 1)
   
    # Dodatkowa kolumna: Czy_Odstające
    df['Czy_Odstające'] = np.where((df['Odstające_IQR'] == -1) |
                                (df['Odstające_mean_std'] == -1) |
                                (df['Odstające_Zscore'] == -1) |
                                (df['Odstające_Winsorizing'] == -1) , True, False)
    df = df.loc[df['Czy_Odstające'] == True]
    df = df.rename(columns={0: var})  # Zmiana nazwy kolumny 0 na Nazwa_Zmiennej
    
    # Dodaj kolumnę "ile_ident"
    df['ile_ident'] = df[['Odstające_IQR', 'Odstające_mean_std', 'Odstające_Zscore', 'Odstające_Winsorizing']].apply(lambda row: row.value_counts().get(-1, 0), axis=1)
    
    return df
Code
zmienna_liczbowa = ['wiek', 'liczba osób w rodzinie',  'dochody', 'wydatki', 'średnia częstość zakupów w tyg']

for kolumna in zmienna_liczbowa:
    wynik = outliers(baza, kolumna)
    if not wynik.empty:
        print(f' zmienna "{kolumna}" zawiera obserwacje nietypowe :')
        display(wynik)
    else:
        print (f' zmienna "{kolumna}" nie zawiera obserwacji nietypowych')
 zmienna "wiek" nie zawiera obserwacji nietypowych
 zmienna "liczba osób w rodzinie" nie zawiera obserwacji nietypowych
 zmienna "dochody" zawiera obserwacje nietypowe :
dochody Odstające_IQR Odstające_mean_std Odstające_Zscore Odstające_Winsorizing Czy_Odstające ile_ident
22 26000.0 1 1 -1 1 True 1
62 61000.0 1 1 -1 1 True 1
77 61000.0 1 1 -1 1 True 1
83 60000.0 1 1 -1 1 True 1
 zmienna "wydatki" zawiera obserwacje nietypowe :
wydatki Odstające_IQR Odstające_mean_std Odstające_Zscore Odstające_Winsorizing Czy_Odstające ile_ident
33 4200.0 -1 -1 -1 1 True 3
40 6084.0 1 1 -1 1 True 1
48 5160.0 -1 1 -1 1 True 2
49 4200.0 -1 -1 -1 1 True 3
96 14964.0 1 1 -1 1 True 1
115 14964.0 1 1 -1 1 True 1
120 14964.0 1 1 -1 1 True 1
 zmienna "średnia częstość zakupów w tyg" nie zawiera obserwacji nietypowych

3.4.2. Obsługa obserwacji odstających (usuwanie, zamiana na NaN, implemetacja estymatorami, dowolną wartością, transformacje)

Dochody_netto–> Wynika, że są to naturalnie obserwacje,zgodne z wiedzą dziedzinową. wydatki_na_żywność –> zidentyfikowane dane są zgodne z dziedzina, ale tak skrajne obserwacje mogą zakłucać rozkład, dlatego zamienimy na NaN, następnie dokonać imputacji

Code
 # Numer indeksów, dla których chcemy zmienić wartości na NaN
indexes_to_replace = [33,49]

for index in indexes_to_replace:
    baza.at[index, 'wydatki'] = np.nan
Code
baza['wydatki'].fillna(baza['wydatki'].mean(), inplace=True)
braki_sprawdzenie(baza)
Analiza brakujących danych:
=============================================
W tabeli nie stwierdzono brakujących danych!

3.4.3. Sprawdzenie czy w zmiennych kategorialnych wystepują obserwacje odstające ( naturalne {obserwacje rzadkie, wysoka kardynalnosć}, błędy)

Code
zmienna_tekstowa= ['płeć', 'wykształcenie',
 'preferowany typ sklepu','preferowana marka sklepu', 'preferowanay towar', 'czynnik zakupowy',
 'rodzaj promocji']

for element in zmienna_tekstowa:
    print(baza[element].value_counts(normalize =True)*100)
    print('-'*35)
płeć
k    68.0
m    32.0
Name: proportion, dtype: float64
-----------------------------------
wykształcenie
wyższe        60.0
średnie       20.0
zawodowe      12.8
podstawowe     7.2
Name: proportion, dtype: float64
-----------------------------------
preferowany typ sklepu
supermarket    30.4
osiedlowy      24.0
bazarek        23.2
galeria        22.4
Name: proportion, dtype: float64
-----------------------------------
preferowana marka sklepu
Biedronka     26.4
Lidl          16.8
Kaufland      11.2
Netto         11.2
Żabka          9.6
Aldi           8.0
Dino           4.8
Carrefour      4.8
Polomarket     1.6
Intermache     1.6
Społem         1.6
Groszek        0.8
Lewiatan       0.8
Jeżyk          0.8
Name: proportion, dtype: float64
-----------------------------------
preferowanay towar
produkty piekarnicze    18.4
owoce i warzywa         17.6
mięso i wędliny         16.0
produkty mleczne        15.2
napoje                  14.4
słodycze                 9.6
produkty zbożowe         8.8
Name: proportion, dtype: float64
-----------------------------------
czynnik zakupowy
jakość                   37.6
cena                     30.4
marka                    16.8
skład                     5.6
dostępność                4.0
lokalność                 1.6
opakowanie                1.6
preferencje kulinarne     1.6
opinie                    0.8
Name: proportion, dtype: float64
-----------------------------------
rodzaj promocji
gazetka             36.0
reklama rtv         27.2
nie korzystam       15.2
karta               11.2
sugestia kasjera     3.2
e-mail               2.4
aplikacja            1.6
nan                  1.6
sms                  1.6
Name: proportion, dtype: float64
-----------------------------------

3.4.4. Obsługa obserwacji odstających (usuwanie, zamiana na NaN, utworzenie nowej kategorii, przypisanie do nowych kategorii zgodnie wiedzą dziedzinową)

Code
# preferowana marka sklepu  # rzadkie i wysoka kardynalnosć - zredukować liczbę poziomów  utworzyć osobną grupę "inne" z poziomami poniżej 5


dict_preferowana_marka_sklepu ={'Aldi':'Inna_lokalna','Dino':'Inna_lokalna',
'Carrefour':'Inna_lokalna',
'Polomarket':'Inna_lokalna',
'Społem':'Inna_lokalna',
'Intermache':'Inna_lokalna',
'Groszek':'Inna_lokalna',
'Jeżyk':'Inna_lokalna',
'Lewiatan':'Inna_lokalna'}

baza['preferowana marka sklepu'] = baza['preferowana marka sklepu'].replace(dict_preferowana_marka_sklepu)
Code
dict_rodzaj_promocji ={
    'inne':'inne',
    'e-mail ':'inne',
   'sms ':'inne',
    'aplikacja ' :'inne',
    'nan ':'inne',
    'sugestia kasjera':'inne'
}
baza['rodzaj promocji'] = baza['rodzaj promocji'].replace(dict_rodzaj_promocji)
Code

czynnik_dict = {'dostępność':'inne',
 'skład':'inne',
 'lokalność':'inne',
 'opinie':'inne',
 'opakowanie':'inne',
 'preferencje kulinarne':'inne'}

baza['czynnik zakupowy'] = baza['czynnik zakupowy'].replace(czynnik_dict)
Code
zmienna_tekstowa= [
 'preferowany typ sklepu','preferowana marka sklepu', 'preferowanay towar', 'czynnik zakupowy',
 'rodzaj promocji']

for element in zmienna_tekstowa:
    display(baza[element].value_counts(normalize=True)*100)
preferowany typ sklepu
supermarket    30.4
osiedlowy      24.0
bazarek        23.2
galeria        22.4
Name: proportion, dtype: float64
preferowana marka sklepu
Biedronka       26.4
Inna_lokalna    24.8
Lidl            16.8
Kaufland        11.2
Netto           11.2
Żabka            9.6
Name: proportion, dtype: float64
preferowanay towar
produkty piekarnicze    18.4
owoce i warzywa         17.6
mięso i wędliny         16.0
produkty mleczne        15.2
napoje                  14.4
słodycze                 9.6
produkty zbożowe         8.8
Name: proportion, dtype: float64
czynnik zakupowy
jakość    37.6
cena      30.4
marka     16.8
inne      15.2
Name: proportion, dtype: float64
rodzaj promocji
gazetka          36.0
reklama rtv      27.2
nie korzystam    15.2
karta            11.2
inne             10.4
Name: proportion, dtype: float64

II. Inżynieria i selekcja cech

4. Wzbogacanie danych

Code


# Tworzenie kategorii wieku
bins = [14, 23, 40, 65, 105]
labels = ['14-22', '23-39', '40-65', '> 66']
baza['wiek_kat'] = pd.cut(baza['wiek'], bins=bins, labels=labels)

# Tworzenie kategorii dochodów
quantiles = [0, 0.25, 0.5, 0.75, 1]
labels = ['niski', 'średni', 'wysoki', 'bardzo wysoki']
baza['dochody_kat'] = pd.qcut(baza['dochody'], q=quantiles, labels=labels)

# Tworzenie kategorii wydatków
baza['wydatki_kat'] = pd.qcut(baza['wydatki'], q=quantiles, labels=labels)

# Tworzenie kategorii wielkości miasta
bins = [0, 50000, 200000, 500000, 5000000]
labels = ['<50 tys.', '51 tys.-200 tys.', '201 tys.-500 tys.', 'pow 500 tys.']
baza['wielkość miasta'] = pd.cut(baza['liczba ludności'], bins=bins, labels=labels)
Code
zmienna= [
 'wiek_kat',    'dochody_kat',  'wydatki_kat',  'wielkość miasta']


for element in zmienna:
    display(baza[element].value_counts(normalize=True).round(2)*100)
wiek_kat
40-65    42.0
23-39    28.0
> 66     21.0
14-22    10.0
Name: proportion, dtype: float64
dochody_kat
niski            29.0
bardzo wysoki    25.0
wysoki           24.0
średni           22.0
Name: proportion, dtype: float64
wydatki_kat
niski            26.0
średni           25.0
wysoki           25.0
bardzo wysoki    25.0
Name: proportion, dtype: float64
wielkość miasta
51 tys.-200 tys.     72.0
201 tys.-500 tys.    19.0
pow 500 tys.          6.0
<50 tys.              3.0
Name: proportion, dtype: float64
Code
braki_sprawdzenie(baza)
Analiza brakujących danych:
=============================================
W tabeli nie stwierdzono brakujących danych!
Code
baza.to_excel('baza.xlsx', index=False)
Code
import ipywidgets as widgets
from IPython.display import display
import altair as alt

baza

# Install matplotlib if not already installed
%pip install matplotlib

import matplotlib.pyplot as plt

# Create a dropdown widget for selecting a column
column_dropdown = widgets.Dropdown(
    options=baza.select_dtypes(include=['float64', 'int64']).columns.tolist(),
    description='Kolumna:',
    disabled=False,
)

# # Create an integer slider widget for selecting the number of bins
# bins_slider = widgets.IntSlider(
#     value=10,
#     min=1,
#     max=50,
#     step=1,
#     description='Bins:',
#     disabled=False,
# )

# # Function to update the histogram based on selected column and bins
# def update_histogram(column, bins):
#     plt.figure(figsize=(10, 6))
#     baza[column].plot(kind='hist', bins=bins, edgecolor='black')
#     plt.title(f'Histogram dla kolumny: {column}')
#     plt.xlabel(column)
#     plt.ylabel('Częstotliwość')
#     plt.grid(True)
#     plt.show()

# # Create an interactive output widget
# output = widgets.interactive_output(update_histogram, {'column': column_dropdown, 'bins': bins_slider})

# # Display the widgets and output
# display(column_dropdown, bins_slider, output)


# Function to update the histogram using Altair
def update_histogram_altair(column, bins):
    chart = alt.Chart(baza).mark_bar().encode(
        alt.X(column, bin=alt.Bin(maxbins=bins), title=column),
        alt.Y('count()', title='Częstotliwość')
    ).properties(
        title=f'Histogram dla kolumny: {column}',
        width=600,
        height=400
    )
    display(chart)

# Create an interactive output widget for Altair
output_altair = widgets.interactive_output(update_histogram_altair, {'column': column_dropdown, 'bins': bins_slider})

# Display the widgets and output for Altair
display(column_dropdown, bins_slider, output_altair)
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: matplotlib in /usr/lib/python3/dist-packages (3.6.3)
Note: you may need to restart the kernel to use updated packages.